package com.ease.util {
   import flash.display.Bitmap;
   import flash.display.BitmapData;
   import flash.display.Loader;
   import flash.display.LoaderInfo;
   import flash.events.Event;
   import flash.utils.ByteArray;

   import mx.core.IFlexDisplayObject;

   /**
    * This class provides image related utilities.
    */
   public class ImageUtil {
      /**
       * Loads a <code>Bitmap</code> from the given <code>ByteArray</code>.
       *
       * @param callback
       *    <code>function(bimap:Bitmap,
       *    error:Error, callContext:Object):void</code>.
       */
      public static function loadBitmap(
         bytes:ByteArray,
         callback:Function,
         callContext:Object=null):void {
         var loader:Loader = new Loader();
         loader.loadBytes(bytes);

         var onLoadComplete:Function = function (event:Event):void {
            var loader:Loader = (event.target as LoaderInfo).loader;
            var bmp:Bitmap = Bitmap(loader.content);
            callback(bmp, callContext);
         }
         loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
      }

      /**
       * Create a  <code>Bitmap</code> object from a string representation of the
       * bitmap image. The string representation should have been generated
       * with the <code>ImageUtil.toString</code> method.
       *
       * @see #toString()
       */
      public static function fromString(value:String, radix:uint = 16):Bitmap {
         if (StringUtils.isNullorEmpty(value)) {
            return null;
         }

         var values:Array = value.split(",");
         if (values.length < 2) {
            // We need at least two items, specifying width and height.
            return null;
         }

         var width:uint = parseInt(values[values.length - 2], radix);
         var height:uint = parseInt(values[values.length - 1], radix);

         if (values.length < (width * height + 2)) {
            // The string does not contain enough data to fill the specified
            // width and height.
            return null;
         }

         var bitmapData:BitmapData = new BitmapData(width, height);
         for (var y:uint = 0; y < height; ++y) {
            for (var x:uint = 0; x < width; ++x) {
               var idx:uint = y * width + x;
               var value:String = values[idx];
               var pixel:uint = parseInt(value, radix);
               bitmapData.setPixel32(x, y, pixel);
            }
         }

         var bitmap:Bitmap = new Bitmap(bitmapData);
         return bitmap;
      }

      /**
       * Convert an image to a string.
       *
       * <p/> The string contains the HEX coding of the image. The format is pretty
       * simple: Image pixels are encoded as HEX values in the so called ARGB format
       * where the first byte is the Alpha channel, separated by comma.
       * The last two items in the string are the image width and height.
       *
       * <p/> In this example, the image is 16x16 pixels:
       * e.g. 00BADC45, 00CCFFAD, ..., 00FFCDBA, 10, 10
       *
       * @param source
       *    An object which you usually pass to <code>Image.source</code> property.
       *    At this point the following instances can be passed:
       *    <ul>
       *       <li>An object that implements IFlexDisplayObject</li>
       *       <li>A <code>Class</code> whose type implements IFlexDisplayObject</li>
       *    </ul>
       *
       * @param radix
       *    The radix which to use when converting pixel values to strings.
       *    Radix 16 is not only more common, but also makes the string smaller.
       *
       * The returned string also contain the Alpha channel value for each pixel.
       */
      public static function toString(source:Object, radix:uint = 16):String {
         var bitmapData:BitmapData = getBitmapData(source);

         if (bitmapData == null) {
            // We cannot process this object.
            return null;
         }

         var retVal:String = toStringFromBitmapData(bitmapData, radix);
         return retVal;
      }

      /**
       * Helper method that actually converts a <code>BitmapData</code> to
       * a string.
       */
      private static function toStringFromBitmapData(
         bitmapData:BitmapData, radix:uint):String {
         var width:uint = bitmapData.width;
         var height:uint = bitmapData.height;

         var size:uint = width * height;
         var values:Array = new Array(size + 2);
         for (var y:uint = 0; y < height; ++y) { // for each row
            for (var x:uint = 0; x < width; ++x) { // for each column
               var pixel:uint = bitmapData.getPixel32(x, y);
               var value:String = pixel.toString(radix);
               values[y*width + x] = value;
            }
         }

         // the last two values are the width and the height.
         values[values.length - 2] = width.toString(radix);
         values[values.length - 1] = height.toString(radix);

         // Now convert this to string.
         var retVal:String = "";

         // Add all items + ",", besides the last. Note that according to this
         // http://www.iwobanas.com/2009/06/string-concatenation/
         // string concatenation in Flex is sufficiently fast.
         for (var idx:uint = 0; idx < values.length-1; ++idx) {
            retVal += values[idx] + ",";
         }

         // Add the last item. No trailing "," character.
         if (idx <= values.length-1) {
            retVal += values[idx];
         }

         return retVal;
      }

      /**
       * Helper method to retrieve the a proper BitmapData instance from
       * the <code>source</code> object.
       *
       * @return Either the <code>BitmapData</code> instance obtained
       *    or <code>null</code> value.
       */
      private static function getBitmapData(source:Object):BitmapData {
         if (source == null) {
            return null;
         }

         var bitmapData:BitmapData = null;
         if (source.hasOwnProperty("bitmapData")) {
            // If the object already has "bitmapData" property, take it from there.
            // to avoid unneeded drawing operation.
            bitmapData = source.bitmapData as BitmapData;
         }

         if (bitmapData != null) {
            return bitmapData;
         }

         var displayObject:IFlexDisplayObject = source as IFlexDisplayObject;
         if (displayObject == null && source is Class) {
            var sourceInstance:Object = new source();
            displayObject = sourceInstance as IFlexDisplayObject;
         }

         if (displayObject != null) {
            // Using 0x00... is important cause we want the image to be transparent
            // by default. So alpha channel has to be 0x00
            bitmapData = new BitmapData(displayObject.width, displayObject.height,
               true /*transparent*/, 0x00FFFFFF);
            bitmapData.draw(displayObject);
         }

         return bitmapData;
      }

   } // class
} // package